use serde::Serialize;
use url::Url;
use std::collections::HashMap;

use crate::http::{HttpBody, HttpContentType, HttpMethod, HttpUri, HttpVersion};
use super::{HttpRequest, HttpRequestHeaders, HttpRequestLine};

#[derive(Debug, thiserror::Error)]
pub enum HttpRequestBuildError {
    #[error("No HTTP method given")]
    NoMethod,
    #[error("No URI path given")]
    NoUri,
    #[error("Invalid URI string '{0}'")]
    InvalidUri(String),
    #[error("Failed to serialize JSON body")]
    SerializeJson,
}

pub struct HttpRequestBuilder {
    pub method: Option<HttpMethod>,
    pub path: Vec<String>,
    pub query: HashMap<String, String>,
    pub version: HttpVersion,
    pub headers: HttpRequestHeaders,
    pub body: Option<HttpBody>,
}

impl HttpRequestBuilder {
    pub fn new() -> Self {
        Self {
            method: None,
            path: vec![],
            query: HashMap::new(),
            version: HttpVersion::V1_1,
            headers: HttpRequestHeaders::default(),
            body: None,
        }
    }

    pub fn get() -> Self {
        Self::new().method(HttpMethod::Get)
    }

    pub fn post() -> Self {
        Self::new().method(HttpMethod::Post)
    }

    pub fn patch() -> Self {
        Self::new().method(HttpMethod::Patch)
    }

    pub fn put() -> Self {
        Self::new().method(HttpMethod::Put)
    }

    pub fn with_headers(headers: HttpRequestHeaders) -> Self {
        Self {
            method: None,
            path: vec![],
            query: HashMap::new(),
            version: HttpVersion::V1_1,
            headers,
            body: None,
        }
    }

    pub fn method(mut self, method: HttpMethod) -> Self {
        self.method = Some(method);
        self
    }

    pub fn uri(mut self, uri_str: &str) -> Result<Self, HttpRequestBuildError> {
        // TODO: do not use url
        let url = Url::parse(uri_str).map_err(|e| HttpRequestBuildError::InvalidUri(e.to_string()))?;
    
        let path_segments = url
            .path_segments()
            .map(|c| c.map(String::from).collect::<Vec<_>>())
            .unwrap_or_default();
        self.path = path_segments;
    
        self.query = url.query_pairs().into_owned().collect();
    
        if let Some(host) = url.host_str() {
            let port = url.port_or_known_default().unwrap_or(80);
            let default_port = match url.scheme() {
                "http" => 80,
                "https" => 443,
                _ => 80,
            };
            let host_header = if port != default_port {
                format!("{}:{}", host, port)
            } else {
                host.to_string()
            };
            self.headers.host = Some(host_header);
        }
    
        Ok(self)
    }

    pub fn path(mut self, path: &str) -> Result<Self, HttpRequestBuildError> {
        let path = HttpUri::parse_path(path)
            .map_err(|e| HttpRequestBuildError::InvalidUri(e.to_string()))?;
        self.path = path;
        Ok(self)
    }

    pub fn query_param<S: Into<String>>(mut self, key: S, value: S) -> Self {
        self.query.insert(key.into(), value.into());
        self
    }

    pub fn version(mut self, version: HttpVersion) -> Self {
        self.version = version;
        self
    }

    pub fn header<S: Into<String>>(mut self, key: S, value: S) -> Self {
        let key = key.into().to_lowercase();
        let value = value.into();
        match key.as_str() {
            "host" => self.headers.host = Some(value),
            "user-agent" => self.headers.user_agent = Some(value),
            "accept" => self.headers.accept = Some(value),
            "content-length" => self.headers.content_length = value.parse().ok(),
            "connection" => self.headers.connection = Some(value),
            "authorization" => self.headers.authorization = Some(value),
            "cookie" => self.headers.cookie = Some(value),
            _ => {
                self.headers.other.insert(key, value);
            }
        }
        self
    }

    pub fn content_type(mut self, content_type: HttpContentType) -> Self {
        self.headers.content_type = Some(content_type);
        self
    }

    pub fn body<B: Into<Vec<u8>>>(mut self, data: B) -> Self {
        let bytes = data.into();
        self.headers.content_length = Some(bytes.len() as u64);
        self.body = Some(HttpBody { data: bytes });
        self
    }

    pub fn text(mut self, text: &str) -> Self {
        self.headers.content_type = Some(HttpContentType::TextPlain);
        self.body(text.to_string())
    }

    pub fn json<T: Serialize>(mut self, value: &T) -> Result<Self, HttpRequestBuildError> {
        let json = serde_json::to_vec(value).map_err(|_| HttpRequestBuildError::SerializeJson)?;
        self.headers.content_type = Some(HttpContentType::ApplicationJson);
        Ok(self.body(json))
    }

    pub fn host<S: Into<String>>(mut self, host: S) -> Self {
        self.headers.host = Some(host.into());
        self
    }

    pub fn user_agent<S: Into<String>>(mut self, ua: S) -> Self {
        self.headers.user_agent = Some(ua.into());
        self
    }

    pub fn authorization<S: Into<String>>(mut self, token: S) -> Self {
        self.headers.authorization = Some(token.into());
        self
    }

    pub fn cookie<S: Into<String>>(mut self, cookie: S) -> Self {
        self.headers.cookie = Some(cookie.into());
        self
    }

    pub fn connection<S: Into<String>>(mut self, connection: S) -> Self {
        self.headers.connection = Some(connection.into());
        self
    }

    pub fn upgrade<S: Into<String>>(mut self, upgrade: S) -> Self {
        self.headers.upgrade = Some(upgrade.into());
        self
    }

    pub fn sec_websocket_version<S: Into<String>>(mut self, version: S) -> Self {
        self.headers.sec_websocket_version = Some(version.into());
        self
    }

    pub fn sec_websocket_key<S: Into<String>>(mut self, key: S) -> Self {
        self.headers.sec_websocket_key = Some(key.into());
        self
    }

    pub fn build(self) -> Result<HttpRequest, HttpRequestBuildError> {
        let method = self.method.ok_or(HttpRequestBuildError::NoMethod)?;
        if self.path.is_empty() {
            return Err(HttpRequestBuildError::NoUri);
        }

        Ok(HttpRequest {
            line: HttpRequestLine {
                method,
                uri: HttpUri {
                    path: self.path,
                    query: self.query,
                },
                version: self.version,
            },
            headers: self.headers,
            body: self.body,
        })
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_build_simple_get_request() {
        let req = HttpRequestBuilder::get()
            .uri("https://example.com/path/to/resource?name=rust&lang=en")
            .unwrap()
            .user_agent("MyTestAgent/0.1")
            .build()
            .unwrap();

        assert_eq!(req.line.method, HttpMethod::Get);
        assert_eq!(req.line.uri.path, vec!["path", "to", "resource"]);
        assert_eq!(req.line.uri.query.get("name").unwrap(), "rust");
        assert_eq!(req.line.uri.query.get("lang").unwrap(), "en");
        assert_eq!(req.headers.host.as_deref(), Some("example.com"));
        assert_eq!(req.headers.user_agent.as_deref(), Some("MyTestAgent/0.1"));
    }

    #[test]
    fn test_missing_method_should_fail() {
        let result = HttpRequestBuilder::new()
            .uri("https://example.com/abc").unwrap()
            .build();

        assert!(matches!(result, Err(HttpRequestBuildError::NoMethod)));
    }

    #[test]
    fn test_path_only_uri() {
        let req = HttpRequestBuilder::get()
            .uri("http://localhost/api/v1/users")
            .unwrap()
            .build()
            .unwrap();

        assert_eq!(req.line.uri.path, vec!["api", "v1", "users"]);
        assert_eq!(req.headers.host.as_deref(), Some("localhost"));
    }

    #[test]
    fn test_query_param_manual_and_from_uri() {
        let req = HttpRequestBuilder::get()
            .uri("https://api.com/search?q=rust").unwrap()
            .query_param("page", "2")
            .build()
            .unwrap();

        assert_eq!(req.line.uri.query.get("q").unwrap(), "rust");
        assert_eq!(req.line.uri.query.get("page").unwrap(), "2");
    }
}
